package com.myzl.coal.utils;

import com.serotonin.modbus4j.*;
import com.serotonin.modbus4j.base.ModbusUtils;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.IllegalDataAddressException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.ip.tcp.TcpSlave;
import com.serotonin.modbus4j.locator.BaseLocator;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * modbus通讯工具类,采用modbus4j实现
 *
 * @author 91941
 */
@Slf4j
public class Modbus4jUtils {

    public static final int REGISTER_COUNT = 200;
    public static final int SLAVE_ID_DEFAULT = 1;
    public static final short REGISTER_DEFAULT_SHORT_VALUE = 0;
    public static final short REGISTER_SHORT_VALUE_1 = 1;
    public static final short REGISTER_SHORT_VALUE_2 = 2;
    public static final double REGISTER_DEFAULT_DOUBLE_VALUE = 0;
    public static final int REGISTER_START = 40000;
    public static final int REGISTER_40001 = 40001;
    private static final Map<Integer, ModbusSlaveSet> MODBUS_SLAVE_SET_MAP = new ConcurrentHashMap<>();
    private static final ModbusFactory modbusFactory = new ModbusFactory();

    @Data
    @AllArgsConstructor
    public static class Slave{
        Integer slaveId;
        ProcessImageListener processImageListener;
    }
    public static ModbusSlaveSet getTcpSlave(List<Slave> slaves, Integer port) {
        if (port == null) {
            port = ModbusUtils.TCP_PORT;
        }
        TcpSlave tcpSlave = new TcpSlave(ModbusUtils.TCP_PORT, false);
        tcpSlave.setMaxReadRegisterCount(REGISTER_COUNT);
        tcpSlave.setMaxWriteRegisterCount(REGISTER_COUNT);
        //创建保持寄存器
        slaves.stream().forEach(e -> {
            BasicProcessImage basicProcessImage = new BasicProcessImage(e.getSlaveId());
            basicProcessImage.addListener(e.getProcessImageListener());
            Stream.iterate(REGISTER_START, n -> n += 1).limit(REGISTER_COUNT).forEach(ele -> basicProcessImage.setHoldingRegister(ele, REGISTER_DEFAULT_SHORT_VALUE));
            tcpSlave.addProcessImage(basicProcessImage);
        });
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            try {
                tcpSlave.start();
            } catch (ModbusInitException e) {
                e.printStackTrace();
            }
        });
        try {
            TimeUnit.SECONDS.sleep(1);
            MODBUS_SLAVE_SET_MAP.put(port, tcpSlave);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return tcpSlave;
    }

    public static void writeHoldingRegisterByMore(Integer slaveId, Integer port, int offset, List<Object> values) {
        List<Short> shortValueList = values.stream().flatMap(value ->
                getShorts(value).stream()
        ).collect(Collectors.toList());
        writeHoldingRegister(slaveId, port, offset, shortValueList);
    }

    public static void writeHoldingRegisterByType(Integer slaveId, Integer port, int offset, Object value) {
        List<Short> shortValueList = getShorts(value);
        writeHoldingRegister(slaveId, port, offset, shortValueList);
    }

    private static void writeHoldingRegister(Integer slaveId, Integer port, int offset, List<Short> shortValueList) {
        if (shortValueList.isEmpty()){
            log.info("slaveId:{},port:{},offset:{} 无数据写入",slaveId,port,offset);
            return;
        }
        AtomicInteger index = new AtomicInteger(offset);
        shortValueList.forEach(e -> writeHoldingRegister(slaveId, port, index.getAndIncrement(), e));
    }

    public static List<Short> getShorts(Object value) {
        List<Short> shortValueList = Collections.EMPTY_LIST;
        if (value instanceof Long) {
            shortValueList = getShorts(value, Long.BYTES);
        } else if (value instanceof Integer) {
            shortValueList = getShorts(value, Integer.BYTES);
        } else if (value instanceof Double) {
            long valueConvert = Double.doubleToLongBits((Double) value);
            shortValueList = getShorts(valueConvert, Double.BYTES);
        } else if (value instanceof Float) {
            int valueConvert = Float.floatToIntBits((Float) value);
            shortValueList = getShorts(valueConvert, Float.BYTES);
        } else if (value instanceof Short) {
            shortValueList = Collections.singletonList((Short) value);
        }
        return shortValueList;
    }

    public static List<Short> getShorts(Object value, int size) {
        int step = 4;
        int len = size * 2;
        String hexValue = String.format("%0" + len + "x", value);
        List<String> hexValueSplit = Stream.iterate(0, n -> n += step).limit(len/step).map(m ->
                hexValue.substring(m, m + step)
        ).collect(Collectors.toList());
        return hexValueSplit.stream().map(m -> (short)Integer.parseInt(m, 16)).collect(Collectors.toList());
    }

    public static boolean writeHoldingRegister(Integer slaveId, Integer port, int offset, short value) {
        if (port == null) {
            port = ModbusUtils.TCP_PORT;
        }
        if (slaveId == null) {
            slaveId = SLAVE_ID_DEFAULT;
        }

        ModbusSlaveSet modbusSlaveSet = MODBUS_SLAVE_SET_MAP.get(port);
        if (modbusSlaveSet==null){
            log.info("writeHoldingRegister no start slaveId:{},port:{},offset:{},value:{},hexValue:{}",slaveId,port,offset,value,Integer.toHexString(value));
            return false;
        }
        ProcessImage processImage = modbusSlaveSet.getProcessImage(slaveId);
        try {
            processImage.writeHoldingRegister(offset, value);
            log.info("writeHoldingRegister slaveId:{},port:{},offset:{},value:{},hexValue:{}",slaveId,port,offset,value,Integer.toHexString(value));
        } catch (IllegalDataAddressException e) {
            log.error("writeHoldingRegister IllegalDataAddressException",e);
        }
        return true;
    }

    public static Short getHoldingRegister(Integer slaveId, Integer port, int offset) {
        if (port == null) {
            port = ModbusUtils.TCP_PORT;
        }
        if (slaveId == null) {
            slaveId = SLAVE_ID_DEFAULT;
        }

        ModbusSlaveSet modbusSlaveSet = MODBUS_SLAVE_SET_MAP.get(port);
        ProcessImage processImage = modbusSlaveSet.getProcessImage(slaveId);
        Short holdingRegister = null;
        try {
            holdingRegister = processImage.getHoldingRegister(offset);
        } catch (IllegalDataAddressException e) {
            e.printStackTrace();
        }
        return holdingRegister;
    }


    public static ModbusMaster getMaster(String ip, Integer port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(ip);
        params.setPort(port);
        ModbusMaster master = modbusFactory.createTcpMaster(params, false);
        master.init();
        return master;
    }

    public static Number readHoldingRegister(String ip, int port, int slaveId, int offset, int dataType)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
        return getMaster(ip, port).getValue(loc);
    }

}

